package mapmaker.map;

import mapmaker.config.Config;
import mapmaker.pose.Position;

public class MapCell {
	
	private static final int MAX_OCCUPIED = 15;
	private static final int MIN_FREE = 0;
	private static final int OCCUPIED_COEFF = 3;
	private static final int FREE_COEFF = -1;
	
	
	private int _weight;
	private Position _bottomLeft;
	private Position _topRight;
	private int _depth;
	
	private MapCell _parent;
	private int _indexRow;
	private int _indexCol;
	private MapCell[][] _children;
	private boolean _seen;
	private boolean _unreachable;
	
	public MapCell(Position botLeft, Position topRight, int indexRow, int indexCol){
		_weight = Config.SUBDIVISION_THRESHOLD;
		_bottomLeft = botLeft;
		_topRight = topRight;
		_depth = 0;
		_indexRow = indexRow;
		_indexCol = indexCol;
		_children = null;
		_parent = null;
		_seen = false;
		_unreachable = false;
	}
	
	/**
	 * Split the cell in 4 children
	 */
	public void addChildren(){
		double distanceX = Math.abs(_bottomLeft.getX() - _topRight.getX());
		double distanceY = Math.abs(_topRight.getY() - _bottomLeft.getY());
		double dx = distanceX/2;
		double dy = distanceY/2;
		_children = new MapCell[2][2];
		for(int i = 0; i < _children.length; i++){
			for(int j = 0; j < _children[i].length; j++){
				Position cellBotLeft = new Position(_bottomLeft.getX() + j * dx, _bottomLeft.getY() + i * dy, 0);
				Position cellTopRight = new Position(_bottomLeft.getX() + j * dx + dx, _bottomLeft.getY() + i * dy + dy, 0);
				_children[i][j] = new MapCell(cellBotLeft, cellTopRight, i, j);
				_children[i][j].setParent(this);
				_children[i][j].setDepth(_depth + 1);
				_children[i][j].setWeight(_weight / Config.WEIGHT_POND);
			}
		}
	}
	
	/**
	 * Remove children from cell
	 */
	public void removeChildren(){
		_children = null;
	}
	
	/**
	 * Set the weight of the cell
	 * @param w
	 */
	public void setWeight(int w){
		_weight = w;
	}
	
	
	/**
	 * Tell if a position is in the cell
	 * @param p the position
	 * @return true if the position is between cell coordinate, false otherwise
	 */
	public boolean positionIsIn(Position p){
		return p.getX() >= _bottomLeft.getX() && p.getX() <= _topRight.getX() && p.getY() >= _bottomLeft.getY() && p.getY() <= _topRight.getY();
	}
	
	/**
	 * Set the parent of a cell
	 * @param parent
	 */
	public void setParent(MapCell parent){
		_parent = parent;
	}
	
	public MapCell getParent(){
		return _parent;
	}
	
	/**
	 * @return A position corresponding to the middle of the cell
	 */
	public Position getMiddleCell(){
		return new Position((_bottomLeft.getX() + _topRight.getX()) / 2.0,
				(_bottomLeft.getY() + _topRight.getY()) / 2.0,
				0);
	}
	
	/**
	 * @return the index of the row in the cell subdivision
	 */
	public int getIndexRow(){
		return _indexRow;
	}
	
	/**
	 * @return the index of the column in the cell subdivision
	 */
	public int getIndexCol(){
		return _indexCol;
	}
	
	public void setDepth(int depth){
		_depth = depth;
	}
	
	public int getDepth(){
		return _depth;
	}
	
	public Position getBottomLeft(){
		return _bottomLeft;
	}
	
	public Position getTopRight(){
		return _topRight;
	}
	
	/**
	 * Set the cell as occupied (increment its weight by 3) and create children (according to some condition @see code)
	 */
	public void setOccupied(){
		if((_weight + OCCUPIED_COEFF) <= MAX_OCCUPIED){
			_weight += OCCUPIED_COEFF;
		}else{
			_weight = MAX_OCCUPIED;
		}
		_seen = true;
		if(_weight == MAX_OCCUPIED) {
			_unreachable = true;
		}
		if(!_unreachable && _weight >= Config.SUBDIVISION_THRESHOLD && _children == null && _depth < Config.DEPTH_MAX){
			addChildren();
		}
	}
	
	/**
	 * @return true if the cell has already been seen, false otherwise 
	 */
	public boolean hasBeenSeen(){
		return _seen;
	}
	
	/**
	 * Set the cell as free (decrement its weight by 1) and set it as reachable if it was
	 */
	public void setFree(){
		if(_weight + FREE_COEFF >= MIN_FREE){
			_weight += FREE_COEFF;
		}else{
			_weight = MIN_FREE;
		}
		_seen = true;
		_unreachable = false;
	}
	
	/**
	 * @return true if the cell has children, false otherwise
	 */
	public boolean hasChildren(){
		return _children != null;
	}
	
	/**
	 * Get the children of the cell
	 * @return
	 */
	public MapCell[][] getChildren(){
		return _children;
	}

	@Override
	public String toString(){
		return "MapCell [\n" +
				"\tbotLeft : " + _bottomLeft + ", topRight : " + _topRight + "\n" +
				"\tWeight : " + _weight + ", Depth : " + _depth + "\n" +
				"\tIndexRow : " + _indexRow + ", _indexCol : " + _indexCol + "\n" +
				"\tHas been seen : " + _seen + "\n" +
				"\tReachable : " + !_unreachable + 
						"\n]";
	}

	/**
	 * Get the weight of the cell
	 * @return
	 */
	public int getWeight() {
		return _weight;
	}

	/**
	 * Set the cell as unreachable and occupied (without splitting it)
	 */
	public void setUnreachable() {
		_unreachable = true;
		setOccupied();
	}

	/**
	 * @return true if the cell is unreachable, false otherwise
	 */
	public boolean isUnreachable() {
		return _unreachable;
	}
}
